/*
    Copyright (C) 2005-2008  Ricky Zheng <ricky_gz_zheng@yahoo.co.nz>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/
/**
 * \file uffs_badblock.c
 * \brief bad block checking and recovering
 * \author Ricky Zheng, created in 13th Jun, 2005
 */

#include "uffs/uffs_fs.h"
#include "uffs/uffs_config.h"
#include "uffs/uffs_ecc.h"
#include <string.h>

#define PFX "badblock:"

void uffs_InitBadBlock(uffs_Device *dev)
{
	dev->bad.block = UFFS_INVALID_BLOCK;//0xfffe
}

/** 
 * \brief check ECC in buf, if the data is corrupt, try ECC correction.
 * \param[in] dev uffs device
 * \param[in] buf data in buf
 * \param[in] block num of this buf
 * \return return U_SUCC if data is valid or collected by ECC successful,
 *         return U_FAIL if ECC fail.
 * \note if a bad block is found, it will then be add to dev->bad, so that we can
 *       deal with it later by calling #uffs_RecoverBadBlock.
 */
URET uffs_CheckBadBlock(uffs_Device *dev, uffs_Buf *buf, int block)
{
	u8 ecc[MAX_ECC_LENGTH];
	int ret;

	dev->flash->MakeEcc(dev, buf->data, ecc);
	ret = dev->flash->EccCollect(dev, buf->data, buf->ecc, ecc);
	if(ret > 0) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"bad block(%d) found but corrected by ecc!\n", block);

		if (dev->bad.block == block) {
			uffs_Perror(UFFS_ERR_NORMAL, PFX"The bad block %d has been reported before.\n", block);
			return U_SUCC;
		}

		if (dev->bad.block != UFFS_INVALID_BLOCK) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"uffs can't handle more than one bad block!\n");
			return U_FAIL;
		}
		/**
		 * mark this block as a new discovered bad block,
		 * so that we can deal with this bad block later.(by calling #uffs_RecoverBadBlock)
		 */
		dev->bad.block = block;
		return U_SUCC;
	}
	else if(ret < 0) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"bad block(%d) found and can't be corrected by ecc!\n", block);
		if (dev->bad.block == block) {
			uffs_Perror(UFFS_ERR_NORMAL, PFX"The bad block %d has been reported before.\n", block);
			return U_FAIL;
		}

		if(dev->bad.block != UFFS_INVALID_BLOCK) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"uffs can't handle more than one bad block!\n");
			return U_FAIL;
		}
		/**
		 * Don't know how can we do, but set treat it as a new discovered bad block anyway.
		 */
		dev->bad.block = block;
		return U_FAIL;
	}
	
	//no error found
	return U_SUCC;
}

/** 
 * \brief recover bad block
 * \param[in] dev uffs device
 */
void uffs_RecoverBadBlock(uffs_Device *dev)
{
	TreeNode *good, *bad;
	uffs_Buf *buf;
	u16 i;
	u16 page;
	uffs_blockInfo *bc = NULL;
	uffs_Tags *tag;
	uffs_Tags newTag;
	UBOOL succRecov;
	URET ret;
	int region;
	u8 type;
	
	if(dev->bad.block == UFFS_INVALID_BLOCK) return;

	//FIX ME!! TODO: recover the bad block to a good block
	good = uffs_GetErased(dev);
	if(good == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"no free block to replace bad block!\n");
		return;
	}

	//recover block
	bc = uffs_GetBlockInfo(dev, dev->bad.block);
	
	if(bc == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get bad block info\n");
		return;
	}

	succRecov = U_TRUE;
	for(i = 0; i < dev->attr.pages_per_block; i++) {
		page = uffs_FindPageInBlockWithPageId(dev, bc, i);
		if(page == UFFS_INVALID_PAGE) {
			break;  //end of last valid page, normal break
		}
		page = uffs_FindBestPageInBlock(dev, bc, page);
		tag = &(bc->spares[page].tag);
		buf = uffs_BufClone(dev, NULL);
		if(buf == NULL) {	
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"Can't clone a new buf!\n");
			succRecov = U_FALSE;
			break;
		}
		//NOTE: since this is a bad block, we can't guarantee the data is ECC ok, so just load data even ECC is not OK.
		ret = uffs_LoadPhiDataToBufEccUnCare(dev, buf, bc->blockNum, page);
		if(ret == U_FAIL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"I/O error ?\n");
			uffs_BufFreeClone(dev, buf);
			succRecov = U_FALSE;
			break;
		}
		buf->dataLen = tag->dataLength;
		if(buf->dataLen > dev->com.pgDataSize) {
			uffs_Perror(UFFS_ERR_NOISY, PFX"data length over flow!!!\n");
			buf->dataLen = dev->com.pgDataSize;
		}

		buf->father = tag->father;
		buf->serial = tag->serial;
		buf->type = tag->type;
		buf->pageID = tag->pageID;
		
		newTag = *tag;
		newTag.blockTimeStamp = uffs_GetNextBlockTimeStamp(tag->blockTimeStamp);
		ret = uffs_WriteDataToNewPage(dev, good->u.list.block, i, &newTag, buf);
		uffs_BufFreeClone(dev, buf);
		if(ret != U_SUCC) {
			uffs_Perror(UFFS_ERR_NORMAL, PFX"I/O error ?\n");
			succRecov = U_FALSE;
			break;
		}
	}


	if(succRecov == U_TRUE) {
		//successful recover bad block, so need to mark bad block, and replace with good one

		region = SEARCH_REGION_DIR|SEARCH_REGION_FILE|SEARCH_REGION_DATA;
		bad = uffs_FindNodeByBlock(dev, dev->bad.block, &region);
		if(bad != NULL) {
			switch(region){
			case SEARCH_REGION_DIR:
				bad->u.dir.block = good->u.list.block;
				type = UFFS_TYPE_DIR;
				break;
			case SEARCH_REGION_FILE:
				bad->u.file.block = good->u.list.block;
				type = UFFS_TYPE_FILE;
				break;
			case SEARCH_REGION_DATA:
				bad->u.data.block = good->u.list.block;
				type = UFFS_TYPE_DATA;
			}
			
			//from now, the 'bad' is actually good block :)))
			uffs_Perror(UFFS_ERR_NOISY, PFX"new bad block %d found, and replaced by %d!\n", dev->bad.block, good->u.list.block);
			dev->ops->EraseBlock(dev, dev->bad.block);
			dev->flash->MakeBadBlockMark(dev, dev->bad.block);
			uffs_ExpireBlockInfo(dev, bc, UFFS_ALL_PAGES);

			//Now, we need to turn the good into 'bad', and put it into bad block list
			good->u.list.block = dev->bad.block;
			uffs_InsertToBadBlockList(dev, good);

			//clear bad block mark.
			dev->bad.block = UFFS_INVALID_BLOCK;
		}
		else {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't find the reported bad block in the tree???\n");
			dev->ops->EraseBlock(dev, good->u.list.block);
			uffs_InsertToErasedListTail(dev, good);
		}
	}
	else {
		dev->ops->EraseBlock(dev, good->u.list.block);
		uffs_InsertToErasedListTail(dev, good); //put back to erased list
	}

	uffs_PutBlockInfo(dev, bc);

}
